package me.lwn.auth.security.web.authentication;

import me.lwn.auth.security.utils.Constants;
import me.lwn.auth.security.utils.HttpUtils;
import me.lwn.auth.security.utils.LocalMessageSource;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 抽象类，用户登录失败时，根据认证服务设置的条件进行锁定用户
 */
public abstract class LoginLockedSupport {

    private final String USER_LOGIN_LOCKED_PREFIX = "oauth2:locked:";
    private final String USER_LOGIN_LOCKED_IP_PREFIX_KEY = "ip:";
    private final String USER_LOGIN_LOCKED_USER_PREFIX_KEY = "user:";
    protected MessageSourceAccessor messages = LocalMessageSource.getAccessor();
    private String prefix = USER_LOGIN_LOCKED_PREFIX;

    protected abstract Integer get(HttpServletRequest request);

    public void count(HttpServletRequest request, HttpServletResponse response, String username, String message) {
        RegisteredClient client = getRegisteredClient(getClientId(request));
        Integer failLockTime = client.getClientSettings().getSetting(Constants.ClientSetting.USER_LOGIN_FAIL_LOCK_TIMES.getKey());
        if (failLockTime != null && failLockTime > 0) {
            doCount(request, response, message, username,
                    client.getClientSettings().getSetting(Constants.ClientSetting.USER_LOGIN_FAIL_LOCK_TIMES.getKey()),
                    client.getClientSettings().getSetting(Constants.ClientSetting.LOGIN_LOCKED_DURATION.getKey()));
        }

        //登陆失败日志
        String clientId = getClientId(request);
        String ip = HttpUtils.getIpAddress(request);
        loginRecord(clientId, ip, username, message, false);
    }

    protected abstract void loginRecord(String clientId, String ip, String username, String reason, boolean status);

    protected abstract void lockedRecord(String clientId, String ip, String username, long duration);

    protected abstract void doCount(HttpServletRequest request, HttpServletResponse response, String username, String message, int times, int duration);

    protected abstract RegisteredClient getRegisteredClient(String clientId);

    protected String getKey(HttpServletRequest request) {
        String clientId = getClientId(request);
        RegisteredClient client = getRegisteredClient(clientId);
        Boolean ipLock = client.getClientSettings().getSetting(Constants.ClientSetting.IP_LOGIN_FAIL_LOCKED.getKey());

        if (ipLock != null && ipLock) {
            return prefix + USER_LOGIN_LOCKED_IP_PREFIX_KEY + HttpUtils.getIpAddress(request).replace(":", "_");
        }
        String username = StringUtils.hasText(request.getParameter("username")) ? request.getParameter("username") : request.getParameter("mobile");
        return prefix + USER_LOGIN_LOCKED_USER_PREFIX_KEY + HttpUtils.getIpAddress(request).replace(":", "_") + ":" + username;
    }

    protected String getKey(String clientId, String ip, String username) {
        RegisteredClient client = getRegisteredClient(clientId);
        Boolean ipLock = client.getClientSettings().getSetting(Constants.ClientSetting.IP_LOGIN_FAIL_LOCKED.getKey());
        if (ipLock != null && ipLock) {
            return prefix + USER_LOGIN_LOCKED_IP_PREFIX_KEY + ip.replace(":", "_");
        }
        return prefix + USER_LOGIN_LOCKED_USER_PREFIX_KEY + ip.replace(":", "_") + ":" + username;
    }

    protected String getIpLockedPrefix() {
        return prefix + USER_LOGIN_LOCKED_IP_PREFIX_KEY;
    }

    protected String getUserLockedPrefix() {
        return prefix + USER_LOGIN_LOCKED_USER_PREFIX_KEY;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public void check(HttpServletRequest request) throws AuthenticationException {
        if (!release(request)) {
            OAuth2Error error = new OAuth2Error("account_locked", messages.getMessage("LoginLockedSupport.locked"), "");
            throw new OAuth2AuthenticationException(error, new LockedException(messages.getMessage("LoginLockedSupport.locked")));
        }
    }

    protected String getClientId(HttpServletRequest request) {
        String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID);
        if (!StringUtils.hasText(clientId)) {
            clientId = HttpUtils.getOAuthParameter(request, null, OAuth2ParameterNames.CLIENT_ID);
        }
        return clientId;
    }

    protected boolean release(HttpServletRequest request) {
        Integer count = get(request);
        RegisteredClient client = getRegisteredClient(getClientId(request));
        Integer lockedTimes = client.getClientSettings().getSetting(Constants.ClientSetting.USER_LOGIN_FAIL_LOCK_TIMES.getKey());
        return lockedTimes == null || lockedTimes < 0 || count < lockedTimes;
    }

    protected abstract void remove(HttpServletRequest request, String username, String message);

    public abstract void remove(String clientId, String ip, String username);

    public abstract void removeAll();
}
